/*
 * $Id: BasicTitledPanelUI.java 3927 2011-02-22 16:34:11Z kleopatra $
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package org.jdesktop.swingx.plaf.basic;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.plaf.BorderUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;

import org.jdesktop.swingx.JXPanel;
import org.jdesktop.swingx.JXTitledPanel;
import org.jdesktop.swingx.SwingXUtilities;
import org.jdesktop.swingx.plaf.TitledPanelUI;

/**
 * All TitledPanels contain a title section and a content section. The default
 * implementation for the title section relies on a Gradient background. All
 * title sections can have components embedded to the &quot;left&quot; or
 * &quot;right&quot; of the Title.
 *
 * @author Richard Bair
 * @author Jeanette Winzenburg
 * @author rah003
 *
 */
public class BasicTitledPanelUI extends TitledPanelUI {
	private static final Logger LOG = Logger.getLogger(BasicTitledPanelUI.class.getName());

	/**
	 * JLabel used for the title in the Title section of the JTitledPanel.
	 */
	protected JLabel caption;
	/**
	 * The Title section panel.
	 */
	protected JXPanel topPanel;
	/**
	 * Listens to changes in the title of the JXTitledPanel component
	 */
	protected PropertyChangeListener titleChangeListener;

	protected JComponent left;
	protected JComponent right;

	/** Creates a new instance of BasicTitledPanelUI */
	public BasicTitledPanelUI() {
	}

	/**
	 * Returns an instance of the UI delegate for the specified component. Each
	 * subclass must provide its own static <code>createUI</code> method that
	 * returns an instance of that UI delegate subclass. If the UI delegate
	 * subclass is stateless, it may return an instance that is shared by
	 * multiple components. If the UI delegate is stateful, then it should
	 * return a new instance per component. The default implementation of this
	 * method throws an error, as it should never be invoked.
	 */
	public static ComponentUI createUI(JComponent c) {
		return new BasicTitledPanelUI();
	}

	/**
	 * Configures the specified component appropriate for the look and feel.
	 * This method is invoked when the <code>ComponentUI</code> instance is
	 * being installed as the UI delegate on the specified component. This
	 * method should completely configure the component for the look and feel,
	 * including the following:
	 * <ol>
	 * <li>Install any default property values for color, fonts, borders, icons,
	 * opacity, etc. on the component. Whenever possible, property values
	 * initialized by the client program should <i>not</i> be overridden.
	 * <li>Install a <code>LayoutManager</code> on the component if necessary.
	 * <li>Create/add any required sub-components to the component.
	 * <li>Create/install event listeners on the component.
	 * <li>Create/install a <code>PropertyChangeListener</code> on the component
	 * in order to detect and respond to component property changes
	 * appropriately.
	 * <li>Install keyboard UI (mnemonics, traversal, etc.) on the component.
	 * <li>Initialize any appropriate instance data.
	 * </ol>
	 * 
	 * @param c
	 *            the component where this UI delegate is being installed
	 *
	 * @see #uninstallUI
	 * @see javax.swing.JComponent#setUI
	 * @see javax.swing.JComponent#updateUI
	 */
	@Override
	public void installUI(JComponent c) {
		assert c instanceof JXTitledPanel;
		JXTitledPanel titledPanel = (JXTitledPanel) c;
		installDefaults(titledPanel);

		caption = createAndConfigureCaption(titledPanel);
		topPanel = createAndConfigureTopPanel(titledPanel);

		installComponents(titledPanel);
		installListeners(titledPanel);
	}

	protected void installDefaults(JXTitledPanel titledPanel) {
		installProperty(titledPanel, "titlePainter", UIManager.get("JXTitledPanel.titlePainter"));
		installProperty(titledPanel, "titleForeground", UIManager.getColor("JXTitledPanel.titleForeground"));
		installProperty(titledPanel, "titleFont", UIManager.getFont("JXTitledPanel.titleFont"));
		LookAndFeel.installProperty(titledPanel, "opaque", false);
	}

	protected void uninstallDefaults(JXTitledPanel titledPanel) {
	}

	protected void installComponents(JXTitledPanel titledPanel) {
		topPanel.add(caption, new GridBagConstraints(1, 0, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST,
				GridBagConstraints.HORIZONTAL, getCaptionInsets(), 0, 0));
		if (titledPanel.getClientProperty(JXTitledPanel.RIGHT_DECORATION) instanceof JComponent) {
			setRightDecoration((JComponent) titledPanel.getClientProperty(JXTitledPanel.RIGHT_DECORATION));
		}
		if (titledPanel.getClientProperty(JXTitledPanel.LEFT_DECORATION) instanceof JComponent) {
			setLeftDecoration((JComponent) titledPanel.getClientProperty(JXTitledPanel.LEFT_DECORATION));
		}
		// swingx#500
		if (!(titledPanel.getLayout() instanceof BorderLayout)) {
			titledPanel.setLayout(new BorderLayout());
		}
		titledPanel.add(topPanel, BorderLayout.NORTH);
		// fix #1063-swingx: must respect custom border
		if (SwingXUtilities.isUIInstallable(titledPanel.getBorder())) {
			// use uiresource border
			// old was: BorderFactory.createRaisedBevelBorder());
			titledPanel.setBorder(BorderUIResource.getRaisedBevelBorderUIResource());
		}
	}

	protected void uninstallComponents(JXTitledPanel titledPanel) {
		titledPanel.remove(topPanel);
	}

	protected Insets getCaptionInsets() {
		return UIManager.getInsets("JXTitledPanel.captionInsets");
	}

	protected JXPanel createAndConfigureTopPanel(JXTitledPanel titledPanel) {
		JXPanel topPanel = new JXPanel();
		topPanel.setBackgroundPainter(titledPanel.getTitlePainter());
		topPanel.setBorder(BorderFactory.createEmptyBorder());
		topPanel.setLayout(new GridBagLayout());
		topPanel.setOpaque(false);
		return topPanel;
	}

	protected JLabel createAndConfigureCaption(final JXTitledPanel titledPanel) {
		JLabel caption = new JLabel(titledPanel.getTitle()) {
			// #501
			@Override
			public void updateUI() {
				super.updateUI();
				setForeground(titledPanel.getTitleForeground());
				setFont(titledPanel.getTitleFont());
			}
		};
		caption.setFont(titledPanel.getTitleFont());
		caption.setForeground(titledPanel.getTitleForeground());
		return caption;
	}

	/**
	 * Reverses configuration which was done on the specified component during
	 * <code>installUI</code>. This method is invoked when this
	 * <code>UIComponent</code> instance is being removed as the UI delegate for
	 * the specified component. This method should undo the configuration
	 * performed in <code>installUI</code>, being careful to leave the
	 * <code>JComponent</code> instance in a clean state (no extraneous
	 * listeners, look-and-feel-specific property objects, etc.). This should
	 * include the following:
	 * <ol>
	 * <li>Remove any UI-set borders from the component.
	 * <li>Remove any UI-set layout managers on the component.
	 * <li>Remove any UI-added sub-components from the component.
	 * <li>Remove any UI-added event/property listeners from the component.
	 * <li>Remove any UI-installed keyboard UI from the component.
	 * <li>Nullify any allocated instance data objects to allow for GC.
	 * </ol>
	 * 
	 * @param c
	 *            the component from which this UI delegate is being removed;
	 *            this argument is often ignored, but might be used if the UI
	 *            object is stateless and shared by multiple components
	 *
	 * @see #installUI
	 * @see javax.swing.JComponent#updateUI
	 */
	@Override
	public void uninstallUI(JComponent c) {
		assert c instanceof JXTitledPanel;
		JXTitledPanel titledPanel = (JXTitledPanel) c;
		uninstallListeners(titledPanel);
		// JW: this is needed to make the gradient paint work correctly...
		// LF changes will remove the left/right components...
		topPanel.removeAll();
		titledPanel.remove(topPanel);
		titledPanel.putClientProperty(JXTitledPanel.LEFT_DECORATION, left);
		titledPanel.putClientProperty(JXTitledPanel.RIGHT_DECORATION, right);
		caption = null;
		topPanel = null;
		titledPanel = null;
		left = null;
		right = null;
	}

	protected void installListeners(final JXTitledPanel titledPanel) {
		titleChangeListener = new PropertyChangeListener() {
			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				if (evt.getPropertyName().equals("title")) {
					caption.setText((String) evt.getNewValue());
				} else if (evt.getPropertyName().equals("titleForeground")) {
					caption.setForeground((Color) evt.getNewValue());
				} else if (evt.getPropertyName().equals("titleFont")) {
					caption.setFont((Font) evt.getNewValue());
				} else if ("titlePainter".equals(evt.getPropertyName())) {
					topPanel.setBackgroundPainter(titledPanel.getTitlePainter());
					topPanel.repaint();
				}
			}
		};
		titledPanel.addPropertyChangeListener(titleChangeListener);
	}

	protected void uninstallListeners(JXTitledPanel titledPanel) {
		titledPanel.removePropertyChangeListener(titleChangeListener);
	}

	protected void installProperty(JComponent c, String propName, Object value) {
		try {
			BeanInfo bi = Introspector.getBeanInfo(c.getClass());
			for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
				if (pd.getName().equals(propName)) {
					Method m = pd.getReadMethod();
					Object oldVal = m.invoke(c);
					if (oldVal == null || oldVal instanceof UIResource) {
						m = pd.getWriteMethod();
						m.invoke(c, value);
					}
				}
			}
		} catch (Exception e) {
			LOG.log(Level.FINE, "Failed to install property " + propName, e);
		}
	}

	/**
	 * Paints the specified component appropriate for the look and feel. This
	 * method is invoked from the <code>ComponentUI.update</code> method when
	 * the specified component is being painted. Subclasses should override this
	 * method and use the specified <code>Graphics</code> object to render the
	 * content of the component.
	 * <p>
	 * 
	 * PENDING JW: we don't need this, do we - remove!
	 *
	 * @param g
	 *            the <code>Graphics</code> context in which to paint
	 * @param c
	 *            the component being painted; this argument is often ignored,
	 *            but might be used if the UI object is stateless and shared by
	 *            multiple components
	 *
	 * @see #update
	 */
	@Override
	public void paint(Graphics g, JComponent c) {
		super.paint(g, c);
	}

	/**
	 * Adds the given JComponent as a decoration on the right of the title
	 * 
	 * @param decoration
	 */
	@Override
	public void setRightDecoration(JComponent decoration) {
		if (right != null)
			topPanel.remove(right);
		right = decoration;
		if (right != null) {
			topPanel.add(decoration, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,
					GridBagConstraints.NONE, UIManager.getInsets("JXTitledPanel.rightDecorationInsets"), 0, 0));

		}
	}

	@Override
	public JComponent getRightDecoration() {
		return right;
	}

	/**
	 * Adds the given JComponent as a decoration on the left of the title
	 * 
	 * @param decoration
	 */
	@Override
	public void setLeftDecoration(JComponent decoration) {
		if (left != null)
			topPanel.remove(left);
		left = decoration;
		if (left != null) {
			topPanel.add(left, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE,
					UIManager.getInsets("JXTitledPanel.leftDecorationInsets"), 0, 0));
		}
	}

	@Override
	public JComponent getLeftDecoration() {
		return left;
	}

	/**
	 * @return the Container acting as the title bar for this component
	 */
	@Override
	public Container getTitleBar() {
		return topPanel;
	}
}
